/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Operations.Spatial;

import Global.Global;
import Operations.*;
import java.awt.image.BufferedImage;


/**
 *
 * @author littlechild
 */
public class OpNeighbour implements Operation {

    public BufferedImage operate(BufferedImage image, double[] paras) {
        int opnumber=(int)paras[paras.length-1];
        Template templatex=null;
        Template templatey=null;
        IntTemplate intTemplatex=new IntTemplate();
        IntTemplate intTemplatey=new IntTemplate();
        DoubleTemplate dblTemplatex=new DoubleTemplate();
        DoubleTemplate dblTemplatey=new DoubleTemplate();
        switch (opnumber) {
            case Global.opRoberts:
                //initiate differantial template
                intTemplatex.template=new int[2][2];
                intTemplatey.template=new int[2][2];
                intTemplatex.template[0][0]=0;intTemplatex.template[0][1]=1;
                intTemplatex.template[1][0]=-1;intTemplatex.template[1][1]=0;

                intTemplatey.template[0][0]=1;intTemplatey.template[0][1]=0;
                intTemplatey.template[1][0]=0;intTemplatey.template[1][1]=-1;
                templatex=intTemplatex;
                templatey=intTemplatey;
                break;
            case Global.opPrewitt:
                //initiate differantial template
                intTemplatex.template=new int[3][3];
                intTemplatey.template=new int[3][3];
                intTemplatex.template[0][0]=-1;intTemplatex.template[0][1]=0;intTemplatex.template[0][2]=1;
                intTemplatex.template[1][0]=-1;intTemplatex.template[1][1]=0;intTemplatex.template[1][2]=1;
                intTemplatex.template[2][0]=-1;intTemplatex.template[2][1]=0;intTemplatex.template[2][2]=1;

                intTemplatey.template[0][0]=-1;intTemplatey.template[0][1]=-1;intTemplatey.template[0][2]=-1;
                intTemplatey.template[1][0]=0;intTemplatey.template[1][1]=0;intTemplatey.template[1][2]=0;
                intTemplatey.template[2][0]=1;intTemplatey.template[2][1]=1;intTemplatey.template[2][2]=1;
                intTemplatex.factor=intTemplatey.factor=1/3.0;
                templatex=intTemplatex;
                templatey=intTemplatey;
                break;
            case Global.opSobel:
                //initiate differantial template
                intTemplatex.template=new int[3][3];
                intTemplatey.template=new int[3][3];
                intTemplatex.template[0][0]=-1;intTemplatex.template[0][1]=0;intTemplatex.template[0][2]=1;
                intTemplatex.template[1][0]=-2;intTemplatex.template[1][1]=0;intTemplatex.template[1][2]=2;
                intTemplatex.template[2][0]=-1;intTemplatex.template[2][1]=0;intTemplatex.template[2][2]=1;

                intTemplatey.template[0][0]=-1;intTemplatey.template[0][1]=-2;intTemplatey.template[0][2]=-1;
                intTemplatey.template[1][0]=0;intTemplatey.template[1][1]=0;intTemplatey.template[1][2]=0;
                intTemplatey.template[2][0]=1;intTemplatey.template[2][1]=2;intTemplatey.template[2][2]=1;
                intTemplatex.factor=intTemplatey.factor=1/4.0;
                templatex=intTemplatex;
                templatey=intTemplatey;

                break;
            case Global.opISobel:
                //initiate differantial template
                dblTemplatex.template=new double[3][3];
                dblTemplatey.template=new double[3][3];
                double a=Math.sqrt(2.0);
                dblTemplatex.template[0][0]=-1;dblTemplatex.template[0][1]=0;dblTemplatex.template[0][2]=1;
                dblTemplatex.template[1][0]=-a;dblTemplatex.template[1][1]=0;dblTemplatex.template[1][2]=a;
                dblTemplatex.template[2][0]=-1;dblTemplatex.template[2][1]=0;dblTemplatex.template[2][2]=1;

                dblTemplatey.template[0][0]=-1;dblTemplatey.template[0][1]=-a;dblTemplatey.template[0][2]=-1;
                dblTemplatey.template[1][0]=0;dblTemplatey.template[1][1]=0;dblTemplatey.template[1][2]=0;
                dblTemplatey.template[2][0]=1;dblTemplatey.template[2][1]=a;dblTemplatey.template[2][2]=1;
                dblTemplatex.factor=dblTemplatey.factor=1/(2.0+a);
                templatex=dblTemplatex;
                templatey=dblTemplatey;
                break;
            case Global.opLaplacian:
                if(paras.length==3) {
                    int size1=(int)paras[0];
                    int size2=(int)paras[1];
                    intTemplatex=getGaussFilter(size1);
                    intTemplatey=getGaussFilter(size2);
                    int middlex1=(intTemplatex.template[0].length-1)/2;
                    int middley1=(intTemplatex.template.length-1)/2;
                    int middlex2=(intTemplatey.template[0].length-1)/2;
                    int middley2=(intTemplatey.template.length-1)/2;
                    int startx=middlex1-middlex2;
                    int starty=middley1-middley2;
                    for(int i=0;i<=middlex2*2;++i) {
                        for (int j=0;j<=middley2*2;++j) {
                            intTemplatex.template[startx+j][startx+i]-=
                                    (int)((1/intTemplatex.factor)*intTemplatey.factor*intTemplatey.template[j][i]);
                        }
                    }
                    setFactor(intTemplatex);
                    templatex=intTemplatex;
                }else if(paras.length==2) {
                    templatex=getLaplacianFilter((int)paras[0]);
                } else {
                    //can not reach here in this version
                    assert false;
                }
                
                break;
            case Global.opNeighbourAverage:
                intTemplatex.template=new int[3][3];
                intTemplatex.template[0][0]=1;intTemplatex.template[0][1]=1;intTemplatex.template[0][2]=1;
                intTemplatex.template[1][0]=1;intTemplatex.template[1][1]=1;intTemplatex.template[2][2]=1;
                intTemplatex.template[2][0]=1;intTemplatex.template[2][1]=1;intTemplatex.template[2][2]=1;
                intTemplatex.factor=1/9.0;
                templatex=intTemplatex;
                break;
            case Global.opGaussFiltering:
                int size=3;
                templatex=getGaussFilter(3);
                break;
            default:
                return null;
        }
        if(templatex!=null && templatey!=null) {
            return compute(image,templatex,templatey);
        }else if (templatex!=null && templatey==null) {
            return compute(image,templatex);
        }else {
            return null;
        }
    }

    private BufferedImage compute(BufferedImage image,Template templatex) {
        BufferedImage returnImage=new BufferedImage(image.getWidth(),image.getHeight(),image.getType());
        if(templatex instanceof IntTemplate) {
            IntTemplate tmplt=(IntTemplate)templatex;
            int templateWidth=tmplt.template[0].length;
            int templateHeight=tmplt.template.length;
            int templateMiddlex=(templateWidth-1)/2;
            int templateMiddley=(templateHeight-1)/2;
            for (int i=0;i!=image.getWidth();++i) {
                for (int j=0;j!=image.getHeight();++j) {
                    int gray=0;
                    int indexx;
                    int indexy;
                    for(int n=0;n!=templateWidth;++n) {
                        for (int m=0;m!=templateHeight;++m) {
                            indexx=i+n-templateMiddlex;
                            indexy=j+m-templateMiddley;
                            gray+=tmplt.template[m][n]*getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                        }
                    }
                    gray=(int)(gray*tmplt.factor);
                    gray=Math.abs(gray);
                    returnImage.setRGB(i, j, gray|(gray<<8)|(gray<<16));
                }
            }
        }else if (templatex instanceof DoubleTemplate){
            DoubleTemplate tmplt=(DoubleTemplate)templatex;
            int templateWidth=tmplt.template[0].length;
            int templateHeight=tmplt.template.length;
            int templateMiddlex=(templateWidth-1)/2;
            int templateMiddley=(templateHeight-1)/2;
            for (int i=0;i!=image.getWidth();++i) {
                for (int j=0;j!=image.getHeight();++j) {
                    double gray=0;
                    int indexx;
                    int indexy;
                    for(int n=0;n!=templateWidth;++n) {
                        for (int m=0;m!=templateHeight;++m) {
                            indexx=i+n-templateMiddlex;
                            indexy=j+m-templateMiddley;
                            gray+=tmplt.template[m][n]*getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                        }
                    }
                    gray=gray*tmplt.factor;
                    gray=Math.abs(gray); // in case gray is less than 0;
                    returnImage.setRGB(i, j, (((int)gray)<<16)|(((int)gray)<<8)|((int)gray));
                }
            }
        }
        return returnImage;
    }

    private BufferedImage compute(BufferedImage image, Template templatex, Template templatey) {
        BufferedImage returnImage=new BufferedImage(image.getWidth(),image.getHeight(),image.getType());
        if(templatex instanceof IntTemplate) {
            IntTemplate tmpltx=(IntTemplate)templatex;
            int templatexWidth=tmpltx.template[0].length;
            int templatexHeight=tmpltx.template.length;
            int templatexMiddlex=(templatexWidth-1)/2;
            int templatexMiddley=(templatexHeight-1)/2;
            IntTemplate tmplty=(IntTemplate)templatey;
            int templateyWidth=tmplty.template[0].length;
            int templateyHeight=tmplty.template.length;
            int templateyMiddlex=(templateyWidth-1)/2;
            int templateyMiddley=(templateyHeight-1)/2;
            for (int i=0;i!=image.getWidth();++i) {
                for (int j=0;j!=image.getHeight();++j) {
                    int gray1=0;
                    int indexx;
                    int indexy;
                    for(int n=0;n!=templatexWidth;++n) {
                        for (int m=0;m!=templatexHeight;++m) {
                            indexx=i+n-templatexMiddlex;
                            indexy=j+m-templatexMiddley;
                            gray1+=tmpltx.template[m][n]*getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                        }
                    }
                    int gray2=0;
                    for (int m=0;m!=templateyHeight;++m) {
                         for(int n=0;n!=templateyWidth;++n) {
                            indexx=i+n-templateyMiddlex;
                            indexy=j+m-templateyMiddley;
                            gray2+=tmplty.template[m][n]*getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                        }
                    }
                    //System.out.println("i:"+i+";j"+j);
                    //.out.println("gray1"+gray1*tmpltx.factor);
                    //System.out.println("gray2"+gray2*tmplty.factor);
                    int gray=(int)((Math.abs(gray1*tmpltx.factor)+Math.abs(gray2*tmplty.factor)));
                    gray= gray>0xff ? 0xff : gray;
                    returnImage.setRGB(i, j, gray|(gray<<8)|(gray<<16));
                }
            }
        }else if (templatex instanceof DoubleTemplate){
            DoubleTemplate tmpltx=(DoubleTemplate)templatex;
            int templatexWidth=tmpltx.template[0].length;
            int templatexHeight=tmpltx.template.length;
            int templatexMiddlex=(templatexWidth-1)/2;
            int templatexMiddley=(templatexHeight-1)/2;
            DoubleTemplate tmplty=(DoubleTemplate)templatey;
            int templateyWidth=tmplty.template[0].length;
            int templateyHeight=tmplty.template.length;
            int templateyMiddlex=(templateyWidth-1)/2;
            int templateyMiddley=(templateyHeight-1)/2;
            for (int i=0;i!=image.getWidth();++i) {
                for (int j=0;j!=image.getHeight();++j) {
                    double gray1=0;
                    int indexx;
                    int indexy;
                    for(int n=0;n!=templatexWidth;++n) {
                        for (int m=0;m!=templatexHeight;++m) {
                            indexx=i+n-templatexMiddlex;
                            indexy=j+m-templatexMiddley;
                            gray1+=tmpltx.template[m][n]*getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                        }
                    }
                    double gray2=0;
                    for(int n=0;n!=templateyWidth;++n) {
                        for (int m=0;m!=templateyHeight;++m) {
                            indexx=i+n-templateyMiddlex;
                            indexy=j+m-templateyMiddley;
                            gray2+=tmplty.template[m][n]*getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                        }
                    }
                    int gray=(int)((Math.abs(gray1*tmpltx.factor)+Math.abs(gray2*tmplty.factor)));
                    returnImage.setRGB(i, j, gray|(gray<<8)|(gray<<16));
                }
            }
        }
        return returnImage;
    }
    
    IntTemplate getLaplacianFiler(double sigma) {
        int size=(int)(4*sigma+1);
        IntTemplate template=getFilter(new TemplateLaplacianFunction(sigma),size);
        // Balance the negative part and the positive part
        int sumPos=0;
        int sumNeg=0;
        for (int i=0;i!=template.template.length;++i) {
            for(int j=0;j!=template.template[0].length;++j) {
                if(template.template[j][i] > 0)
                    sumPos+=template.template[j][i];
                else
                    sumNeg+=template.template[j][i];
            }
        }
        boolean isAdd=sumPos < -sumNeg;
        //expected move for each weight
        double derivation=Math.abs(sumPos+sumNeg)/(template.template.length*template.template[0].length);
        //just rounding is not a good idea, I use random rounding
        double randomThreshold=derivation-(int)(derivation);
        for (int i=0;i!=template.template.length;++i) {
            for(int j=0;j!=template.template[0].length;++j) {
                template.template[i][j]+=(int)derivation;
                if(Math.random()<randomThreshold) {
                    if(isAdd) {
                        template.template[i][j]+=1;
                    }else {
                        template.template[i][j]-=1;
                    }
                }
            }
        }
        setFactor(template);
        return template;
    }
    IntTemplate getLaplacianFilter(int size) {
        assert size%2==1;
        return getLaplacianFiler((size-1)/4.0);
    }
    IntTemplate getGaussFilter(double sigma) {
        int size=(int)(4*sigma+1);
        return getFilter(new TemplateGaussFunction(sigma),size);
    }
    
    IntTemplate getGaussFilter(int size) {
        assert size%2==1;
        return getGaussFilter((size-1)/4.0);
    }
    IntTemplate getSquareFilter(int radius) {
        
        return getFilter(new TemplateSquareFunction(radius),2*radius+1);
    }
    private IntTemplate getFilter(TemplateFunctionR func,int size) {
        int template[][]=new int[size][size];
        //assign the approximating number for pixels in the region
        double middlex=size/2;
        double middley=size/2;
        for(int i=0;i!=size;++i) {
            for (int j=0;j!=size;++j) {
                double r2=(i-middlex)*(i-middlex)+(j-middley)*(j-middley);
                template[i][j]=(int)(func.getValue(Math.sqrt(r2))*100);
            }
        }
        IntTemplate returnTemplate=new IntTemplate();
        returnTemplate.template=template;
        setFactor(returnTemplate);
        return returnTemplate;
    }
    private void setFactor(IntTemplate a) {
        int sumPos=0;
        int sumNeg=0;
        for(int i=0;i!=a.template[0].length;++i) {
            for (int j=0;j!=a.template.length;++j) {
                if(a.template[j][i] > 0)
                    sumPos+=a.template[j][i];
                else
                    sumNeg+=a.template[j][i];
            }
        }
        if(sumPos>-sumNeg)
            a.factor=1.0/sumPos;
        else
            a.factor=-1.0/sumNeg;
    }
    public static int getGray(int rgb) {
        return (int)(0.5+0.11*(rgb & 255) + 0.3*((rgb>>>16) & 255) +0.59*((rgb>>>8) & 255));
    }
}


